﻿Namespace Abstraction
    ''' <summary>
    ''' Provides unified methods to Read the contents of Arphoon Packages and the bare Filesystem at the same time
    ''' </summary>
    ''' <remarks></remarks>
    Public Class FileSystemAbstraction
        Implements System.IDisposable

#Region "Debug Logging Support"
        Public Shared Property EnableDebugLogging As Boolean = False
        Public Shared Property DebugLoggingFilename As Boolean = True
        Private WithEvents _logFacility As Arps.Diagnostics.LogFacility
        Private Sub AddLog(ByVal text As String)
            If Not EnableDebugLogging Then Exit Sub

            If DebugLoggingFilename Then
                _logFacility.AddLog("[" & _engine.MountedPackage.Package.Name & "]" & text, True, True)
            Else
                _logFacility.AddLog(text, True, True)
            End If

        End Sub
        Private Sub AddLog()
            If Not EnableDebugLogging Then Exit Sub

            _logFacility.AddLog("", False, False)
        End Sub

        Public Event DebugLineGenerated(ByVal text As String)
        Private Sub __bridge_LogFacilityDebugLineGenerated(ByVal text As String) Handles _logFacility.FacilityChanged
            RaiseEvent DebugLineGenerated(text)
        End Sub
#End Region


        ''' <summary>
        ''' Creates a new FileSystem Abstraction with unpacked path support
        ''' </summary>
        ''' <param name="targetPackageFilename">Filename of the target package to load</param>
        ''' <param name="pUnpackedPath">Unpacked path that will be associated with target package</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal targetPackageFilename As String, ByVal pUnpackedPath As String, Optional ByVal activatePackage As Boolean = True)
            _packedFilename = targetPackageFilename
            _unpackedPath = pUnpackedPath

            If activatePackage Then
                ' Activate Package
                Active = True
            End If
        End Sub
        ''' <summary>
        ''' Creates a new FileSystem Abstraction without unpacked path support
        ''' </summary>
        ''' <param name="targetPackageFilename">Filename of the target package to load</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal targetPackageFilename As String, Optional ByVal activatePackage As Boolean = True)
            AllowExternalFiles = False

            _packedFilename = targetPackageFilename

            If activatePackage Then
                ' Activate Package
                Active = True
            End If
        End Sub

        Public Property AllowExternalFiles As Boolean = True
        ''' <summary>
        ''' When false, the Class will release the Arphoon Engine and close the source package when Deactived with the <see>Active</see> property. When activated again, the class will recreate the Arphoon Engine, remounting the package and building content tree (Slow performance)
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>Activating this mode can lead to serious performance impact on the application, since the class will remount the package and build all trees when Reactivated. This process can take a lot of time, specially on large packages</remarks>
        Public Property DisconnectionSupport As Boolean = False
        Private _active As Boolean
        Public Property Active As Boolean
            Get
                Return _active
            End Get
            Set(value As Boolean)
                If value Then
                    ' Activate Engine
                    If Not MountArphoonPackage() = Engine.PackageMountReturnCode.MountSuccess Then Exit Property
                Else
                    If DisconnectionSupport Then
                        ' Deactivate Engine
                        UnmountArphoonPackage()
                    End If
                End If

                _active = value
            End Set
        End Property
        Private _lastReturnCode As Engine.PackageMountReturnCode
        Public ReadOnly Property LastReturnCode As Engine.PackageMountReturnCode
            Get
                Return _lastReturnCode
            End Get
        End Property

        Private _unpackedPath As String
        Private _packedFilename As String

        Private _engine As Engine

        Public ReadOnly Property UnpackedPath As String
            Get
                Return _unpackedPath
            End Get
        End Property
        Public ReadOnly Property PackedFilename As String
            Get
                Return _packedFilename
            End Get
        End Property

        Private Function MountArphoonPackage() As Engine.PackageMountReturnCode
            If _engine IsNot Nothing Then
                _lastReturnCode = Engine.PackageMountReturnCode.MountSuccess
                Return Engine.PackageMountReturnCode.MountSuccess
            End If



            AddLog("Mounting target arphoon package...")

            Try
                _engine = New Engine

                Dim output As Engine.PackageMountReturnCode

                output = _engine.Mount(_packedFilename)

                If output = Engine.PackageMountReturnCode.MountSuccess Then
                    AddLog("Mount success. Recreating package structure...")

                    _engine.BuildDirectoryTreeStructure()
                    _engine.BuildFileTreeStructure()

                    AddLog("Done recreating package structure.")
                Else
                    AddLog("ERROR!: Failed to mount target package! Code: " & output.ToString)
                End If

                _lastReturnCode = output
                Return output
            Catch ex As Exception
                AddLog("FATAL!: Exception while reactivating arphoon package!")
                AddLog("    ex: " & ex.Message)

                _lastReturnCode = Engine.PackageMountReturnCode.InternalException
                Return Engine.PackageMountReturnCode.InternalException
            End Try
        End Function
        Private Sub UnmountArphoonPackage()
            If _engine Is Nothing Then Exit Sub

            _engine.Unmount()
            _engine.Dispose()
            _engine = Nothing

            AddLog("Package Unmounted.")
        End Sub

        ' Operative Functions
        Public Function FileExists(ByVal filename As String) As Boolean
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            Dim piInfo As PackagedFileInformationCollection = _engine.GetFile(filename)

            If ((piInfo.Filename IsNot Nothing) AndAlso (piInfo.Filename.Length > 0)) Then Return True


            If AllowExternalFiles Then
                ' Process and Validate filename
                filename = LCase(filename)
                filename = Replace(filename, "/", "\")
                filename = Replace(filename, "\\", "\")
                If Left(filename, 1) = "\" Then filename = Right(filename, filename.Length - 1) ' Remove any heading '\'
                If Right(filename, 1) = "\" Then filename = Left(filename, filename.Length - 1) ' Remove any leading '\'

                If IO.File.Exists(_unpackedPath & "\" & filename) Then
                    Return True
                Else
                    'AddLog("WARNING: FileNotFound: " & filename)
                    Return False
                End If
            Else
                'AddLog("WARNING: FileNotFound: " & filename)
                Return False
            End If
        End Function
        Public Function DirectoryExists(ByVal directory As String) As Boolean
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            If _engine.GetFiles(directory).Length > 0 Then Return True
            If _engine.GetDirectories(directory).Length > 0 Then Return True

            If AllowExternalFiles Then
                ' Process and Validate filename
                directory = LCase(directory)
                directory = Replace(directory, "/", "\")
                directory = Replace(directory, "\\", "\")
                If Left(directory, 1) = "\" Then directory = Right(directory, directory.Length - 1) ' Remove any heading '\'
                If Right(directory, 1) = "\" Then directory = Left(directory, directory.Length - 1) ' Remove any leading '\'

                If IO.Directory.Exists(_unpackedPath & "\" & directory) Then
                    Return True
                Else
                    AddLog("WARNING: DirectoryNotFound: " & directory)
                    Return False
                End If
            Else
                AddLog("WARNING: DirectoryNotFound: " & directory)
                Return False
            End If
        End Function
        Public Function FileLength(ByVal filename As String) As Long
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            Dim length As Long = _engine.GetFile(filename).Length
            If length > 0 Then Return length

            If AllowExternalFiles Then
                ' Process and Validate filename
                filename = LCase(filename)
                filename = Replace(filename, "/", "\")
                filename = Replace(filename, "\\", "\")
                If Left(filename, 1) = "\" Then filename = Right(filename, filename.Length - 1) ' Remove any heading '\'
                If Right(filename, 1) = "\" Then filename = Left(filename, filename.Length - 1) ' Remove any leading '\'

                If IO.File.Exists(_unpackedPath & "\" & filename) Then
                    Return New IO.FileInfo(_unpackedPath & ".\" & filename).Length
                Else
                    AddLog("WARNING: FileNotFound: " & filename)
                    Return -1
                End If
            Else
                AddLog("WARNING: FileNotFound: " & filename)
                Return -1
            End If
        End Function
        Public Function GetFiles(ByVal path As String, Optional ByVal filter As String = "*") As String()
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            Dim files() As PackagedFileInformationCollection = _engine.GetFiles(path, filter)
            If files.Length > 0 Then
                Dim output(files.Length - 1) As String
                Dim i As Integer
                For i = 0 To output.Length - 1
                    output(i) = files(i).FullName
                Next

                Return output
            End If

            If AllowExternalFiles Then
                ' Process and Validate filename
                path = LCase(path)
                path = Replace(path, "/", "\")
                path = Replace(path, "\\", "\")
                If Left(path, 1) = "\" Then path = Right(path, path.Length - 1) ' Remove any heading '\'
                If Right(path, 1) = "\" Then path = Left(path, path.Length - 1) ' Remove any leading '\'

                Try
                    Dim bareFiles() As String = System.IO.Directory.GetFiles(_unpackedPath & "\" & path, filter)

                    Return bareFiles
                Catch ex As Exception
                    Return Nothing
                End Try
            Else
                Return Nothing
            End If
        End Function
        Public Function GetDirectories(ByVal path As String, Optional ByVal filter As String = "*") As String()
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            Dim dirs() As String = _engine.GetDirectories(path, filter)
            If ((dirs IsNot Nothing) AndAlso (dirs.Length > 0)) Then
                Return dirs
            End If

            If AllowExternalFiles Then
                ' Process and Validate filename
                path = LCase(path)
                path = Replace(path, "/", "\")
                path = Replace(path, "\\", "\")
                If Left(path, 1) = "\" Then path = Right(path, path.Length - 1) ' Remove any heading '\'
                If Right(path, 1) = "\" Then path = Left(path, path.Length - 1) ' Remove any leading '\'

                Try
                    Dim bareDirs() As String = System.IO.Directory.GetDirectories(_unpackedPath & "\" & path, filter)

                    Return bareDirs
                Catch ex As Exception
                    Return Nothing
                End Try
            Else
                Return Nothing
            End If
        End Function
        Public Function GetFileStream(ByVal filename As String) As System.IO.Stream
            If Not Active Then
                AddLog("ERROR: Tried to access a content method when the instance is not ready.")
                Throw New NotSupportedException("Instance not active")
            End If

            Dim file As PackagedFileInformationCollection = _engine.GetFile(filename)
            If ((file.Filename IsNot Nothing) AndAlso (file.Filename.Length > 0)) Then Return file.GetStream()

            If AllowExternalFiles Then
                ' Process and Validate filename
                filename = LCase(filename)
                filename = Replace(filename, "/", "\")
                filename = Replace(filename, "\\", "\")
                If Left(filename, 1) = "\" Then filename = Right(filename, filename.Length - 1) ' Remove any heading '\'
                If Right(filename, 1) = "\" Then filename = Left(filename, filename.Length - 1) ' Remove any leading '\'

                If IO.File.Exists(_unpackedPath & "\" & filename) Then
                    Return New IO.FileStream(_unpackedPath & "\" & filename, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
                Else
                    AddLog("WARNING: FileNotFound: " & filename)
                    Return Nothing
                End If
            Else
                AddLog("WARNING: FileNotFound: " & filename)
                Return Nothing
            End If
        End Function

#Region "IDisposable Support"
        Private disposedValue As Boolean ' Para detectar llamadas redundantes

        ' IDisposable
        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    ' TODO: eliminar estado administrado (objetos administrados).
                End If

                ' TODO: liberar recursos no administrados (objetos no administrados) e invalidar Finalize() below.
                ' TODO: Establecer campos grandes como Null.
            End If
            Me.disposedValue = True
        End Sub

        ' TODO: invalidar Finalize() sólo si la instrucción Dispose(ByVal disposing As Boolean) anterior tiene código para liberar recursos no administrados.
        'Protected Overrides Sub Finalize()
        '    ' No cambie este código. Ponga el código de limpieza en la instrucción Dispose(ByVal disposing As Boolean) anterior.
        '    Dispose(False)
        '    MyBase.Finalize()
        'End Sub

        ' Visual Basic agregó este código para implementar correctamente el modelo descartable.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' No cambie este código. Coloque el código de limpieza en Dispose(disposing As Boolean).
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class
End Namespace
